Skip to content

Add capture history to the capture ordering - #419

Closed
devin-ai-integration[bot] wants to merge 1 commit into
masterfrom
devin/1794547933-capture-history
Closed

devin-ai-integration[bot] wants to merge 1 commit into
masterfrom
devin/1794547933-capture-history

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Captures were ordered by SEE and then MVV-LVA, so nothing learned which captures actually cut. This adds a capture history table to MoveOrdering:

type CaptureHistory = [[[[i32; 6]; 64]; 6]; 2]; // side, piece taking, to square, piece taken
key = band + see * SEE_UNIT + entry * CAPTURE_HISTORY_WEIGHT /* 16 */ + mvv_lva
  • Taught in MoveOrdering::cutoff: a capture that cuts gains d², and every capture in tried loses d², whether a capture or a quiet cut. A capture cutoff still teaches neither quiet memory.
  • Read only where the quiet memories are (ply is Some: main search nodes, not the root or quiescence), on both the keyed and the spilled order_by_sort paths.
  • Bands hold. A full entry (131,072) is short of one pawn of SEE (200,000) by more than a quiet's widest history (16,384) plus the largest tiebreak. So a losing capture stays behind every quiet, and the table can reorder captures a pawn apart but never two. the_bands_do_not_overlap asserts this.
  • Cleared in forget with the other memories.

Pins moved: bench 9,312,660 to 9,972,665 (+7.1%). The tactical suite goes from 231 to 232. The strategic total goes from 104,989 to 104,632, which is inside the 447 noise the total shows under one centipawn nudges. The WAC.021 floor test now uses the new depth nine floors (budget 120,000 to 180,000). The reference search's counts don't change.

Strength is not measured yet. It is to be played as sprt [0, 5] at 10+0.1 against b0600cb.

Tests run: release, optimised debug, machine-test and target-cpu=x86-64 suites, both clippy runs, fmt.

Link to Devin session: https://app.devin.ai/sessions/eecd5bd45e4d476db1e20ed292a722ec
Open in Devin Desktop: https://app.devin.ai/desktop/session/eecd5bd45e4d476db1e20ed292a722ec?variant=devin
Requested by: @aywrite


Devin Review

Captures were ordered by SEE and then MVV-LVA alone, so nothing learned
which captures actually cut. A capture history table, keyed by the side,
the piece taking, the to square and the piece taken, is taught at the
quiet history's cutoff sites: a capture that cuts gains d², and every
capture tried before a cutoff loses d², whether a capture or a quiet
cut. A capture that cuts still teaches neither quiet memory.

It is read where the quiet memories are (the main search's nodes, not
the root or quiescence), on both the keyed and the spilled list paths.
An entry adds 16 a point to the capture's key, so a full entry
(131,072) is short of one pawn of SEE (200,000) by more than a quiet's
widest history and the largest tiebreak. The swap's sign still picks
the band, a losing capture stays behind every quiet, and the table can
reorder captures a pawn apart but never two. `the_bands_do_not_overlap`
checks this, and the table is cleared with the other memories at each
go.

The bench moves 7.1% (9,312,660 to 9,972,665). The reference search
does not read the memories, so its counts hold. The tactical suite goes
from 231 to 232 and the strategic total from 104,989 to 104,632, inside
the 447 the total moves by under one centipawn nudges. The floor test
on WAC.021 is moved to the new depth nine floors.

Co-Authored-By: Andrew Wright <[email protected]>
Bench: 9972665
Elo: not measured
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@github-actions

Copy link
Copy Markdown
Contributor

Speed against b0600cb9

Measured on Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz.
Both sides built with rustc 1.99.1 (58cf8f9c3 2026-10-13).

Both sides built and run on this runner in this job, the way
scripts/speed.sh measures a perf commit. Each round runs
both sides on a layout of its own, the same compiled code with
its code and data shuffled and moved, so the 95% interval carries
where the code landed as well as the run. The verdict holds the
interval against a 1% threshold. The default layout, the one a
release ships, is measured after as a diagnostic. The node
counts are the search's: they move when the search does, and a
speed change leaves them alone.

round     base nps  candidate nps  change
    1      4024042        4066361   +1.1%
    2      4066569        4052680   -0.3%
    3      4073768        4044783   -0.7%
    4      4083502        4069691   -0.3%
    5      4103344        4029395   -1.8%
    6      4050134        3950792   -2.5%
    7      4048938        4047901   -0.0%
    8      4034259        3913046   -3.0%
    9      4022636        4030473   +0.2%
   10      3988938        4059428   +1.8%
   11      4070416        4037128   -0.8%
   12      3974861        4047594   +1.8%
   13      4058383        4062756   +0.1%
   14      4094475        4034479   -1.5%
   15      4069782        4056024   -0.3%
   16      4118029        4090569   -0.7%
   17      4083101        4074401   -0.2%
   18      4141395        4102019   -1.0%
   19      4136979        4102996   -0.8%
   20      3854281        4111362   +6.7%
   21      4085164        4079048   -0.1%
   22      4080952        4094531   +0.3%
   23      4115187        4037037   -1.9%
   24      4072810        4039648   -0.8%
   25      4044964        4077948   +0.8%
   26      4097226        4058678   -0.9%
   27      4086324        4110284   +0.6%
   28      4125053        3931077   -4.7%
   29      4087763        3992996   -2.3%
   30      4143243        4106747   -0.9%
   31      4130412        4095023   -0.9%
   32      4058820        4099680   +1.0%
   33      4083391        4034323   -1.2%
   34      4016222        4108838   +2.3%
   35      4030732        4036762   +0.1%
   36      4113318        4033773   -1.9%
   37      4108522        4089791   -0.5%
   38      4025419        4072894   +1.2%
   39      4103751        4090468   -0.3%
   40      4136946        4099363   -0.9%

             nodes    time  median nps  faster half
base       9312660  2.28 s     4082026      4108856
candidate  9972665  2.46 s     4061092      4090238
change       +7.1%   +7.6%       -0.5%        -0.5%

paired change -0.4%, 95% interval -0.8% to -0.0%
diagnostic, on the default layout alone -0.8%, 95% interval -1.6% to -0.4%, 13 rounds

the node counts differ, so this is a search change as well as a speed
one. nps only says what a node costs. Time to depth is what the change is
worth at this depth, and whether the new tree is the right one is for
games to say.

Speed: -0.4% (bench nps, 95% interval -0.8% to -0.0%, 40 interleaved rounds over shuffled layouts vs b0600cb9)

The instructions each side's bench executed, counted under
cachegrind. The count repeats to within a few hundred
instructions, so a small change here is a real one, but it
prices instructions only: cache misses, mispredicted branches
and where the code lands are the speed's to show.

instructions against b0600cb9, one cachegrind run a side, bench

             instructions    nodes  per node
base       13,159,581,210  9312660    1413.1
candidate  14,081,640,168  9972665    1412.0
change             +7.01%   +7.09%    -0.08%

the trees differ, so the count moved with the search as well as the code; the per node column is the nearer reading

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Closing unmerged. Capture history played +1 ±6 over 6,000 games at 10+0.1 against b0600cb (sprt [0, 5] unresolved at the cap, LLR -0.86), so it does not ship. The result is added to the rejected list in docs/ROADMAP.md.

Written by Devin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant